#include "include/config.h"
#include "Layout.h"

// protect mode cpu data <([{
// gdt gate descriptor: high 8 base, 4-bits type, high 4 limit, 8-bits type, low 24 base, low 16 limit.
long long gdt_table[7] __attribute__((aligned(8))) = {
	0x0,					// null.
	0x00cf9a000000ffffULL,	// core code segment.
	0x00cf92000000ffffULL,	// core data segment.
	0x40c3fa000000ffffULL,	// user code segment. base = 1G, size = 1G (just a sample).
	0x00cffa000000ffffULL,	// user data segment.
	0xbb0089bbbbbb0068ULL,	// tss_descriptor.base is set in post_cpudata_init().
	0xbb0082bbbbbb0000ULL	// ldt_descriptor.base is set in post_cpudata_init().
};

// idt gate descriptor: high 16 offset, 16-bits type, segment selector, low 16 offset.
// 0-31: intel architecture reserved. 32-47: 16 extern interrupt sources. 48: syscall entry.
long long idt_table[48] __attribute__((aligned(8)));

long long ldt_table[0] __attribute__((aligned(8)));

unsigned char* root_page_directory; // physical address.

// The size of core stack is 2 pages.
unsigned char* root_stack;

struct TSS {
	unsigned long		back_link;
	unsigned long		sp0;
	unsigned long		ss0;
	unsigned long		sp1;
	unsigned long		ss1;
	unsigned long		sp2;
	unsigned long		ss2;
	unsigned long		cr3;
	unsigned long		ip;
	unsigned long		flags;
	unsigned long		ax;
	unsigned long		cx;
	unsigned long		dx;
	unsigned long		bx;
	unsigned long		sp;
	unsigned long		bp;
	unsigned long		si;
	unsigned long		di;
	unsigned long		es;
	unsigned long		cs;
	unsigned long		ss;
	unsigned long		ds;
	unsigned long		fs;
	unsigned long		gs;
	unsigned long		ldt;
	unsigned short		trace;
	unsigned short		io_bitmap_base;
} __attribute__((packed)) tss = {
	0,
	0, ZEROIMAGE >> 16,
	0, 0,
	0, 0,
	0,
	0, 0,
	0, 0, 0, 0,
	0, 0,
	0, 0,
	0, 0, 0, 0, 0, 0,
	PROTECT_MODE_LDT,
	0, 0
};
// }])>

// INITTEXT/INITDATA <([{
struct cpu_table_loader {
	unsigned short limit;
	void* address __attribute__((packed));
};
cpu_table_loader INITDATA gdt_addr = {
	sizeof(gdt_table) - 1,
	&gdt_table
};
cpu_table_loader INITDATA idt_addr = {
	sizeof(idt_table) - 1,
	&idt_table
};

static inline void INITTEXT set_gdt_entry(int index, void* addr)
{
	unsigned int address = reinterpret_cast<unsigned int>(addr);
	char* xdt_index = (char*) &gdt_table[index >> 3];
	*((unsigned short*) (xdt_index + 2)) = (unsigned short) address;
	*((unsigned char*) (xdt_index + 4)) = (unsigned char) (address >> 16);
	*((unsigned char*) (xdt_index + 7)) = (unsigned char) (address >> 24);
}

typedef void (*FUNC)(void);
static inline void INITTEXT set_idt_entry(int index, FUNC func, int type)
{
	// IDT entry pattern:
	// 0xbbbb8e000008bbbbULL,	// interrupt gate (dpl = 0).
	// 0xbbbbee000008bbbbULL,	// interrupt gate (dpl = 3).
	// 0xbbbb8f000008bbbbULL,	// trap gate (dpl = 0).
	// 0xbbbbef000008bbbbULL	// trap gate (dpl = 3).
	unsigned int address = reinterpret_cast<unsigned int>(func);
	char* xdt_index = (char*) &idt_table[index];
	*((unsigned short*) (xdt_index)) = (unsigned short) (address);
	*((unsigned short*) (xdt_index + 6)) = (unsigned short) (address >> 16);
}

// Continue to initialize protect mode cpu data, in fact, the job can be finished in bootmain.S too, but C++ can do it easier and we can use gdb to debug it.
//
// Jobs finished in bootmain.S are cr0, cr3, cr4, root_page_directory, gdt.
//
// Jobs finished here are tr, ldt, idt, tss, gdt_table (part), idt_table.
void INITTEXT post_cpudata_init()
{
	tss.cr3 = reinterpret_cast<unsigned long>(root_page_directory);
	tss.sp0 = reinterpret_cast<unsigned long>(root_stack);

	// The later code initializes gdt/idt tables, although all entries in gdb/idt have static linkage address, but unfortunately, descriptor format is too complex to ld to finish the job directly.
	set_gdt_entry(PROTECT_MODE_TSS, &tss);
	asm volatile ("ltr %%ax" : : "a" (PROTECT_MODE_TSS));
	set_gdt_entry(PROTECT_MODE_LDT, &ldt_table);
	asm volatile ("lldt %%ax" : : "a" (PROTECT_MODE_LDT));

	// An erroneous table, so when there is any error, make bochs catch it for debug purpose.
	for (int i = 0; i < 49; i++)
		idt_table[i] = (long long) 0x8000 << 32;
	asm volatile ("lidt %0" : : "m" (idt_addr));
	/*
	set_idt_entry(0 , &IDTEntry::divide_error);
	*/
}
// }])>

// vim: foldmarker=<([{,}])> foldmethod=marker
